package com.woniu.aspect;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.woniu.annotation.OperateLog;
import com.woniu.entity.SysOperateLog;
import com.woniu.service.SysOperateLogService;
import com.woniu.utils.JsonUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author pml
 * @Date 2025/6/19 15:31
 * @Description 操作日志AOP切面
 * 环绕通知处理所有标记@OperateLog注解的方法
 */
@Aspect
@Component
public class OperateLogAspect {

    @Autowired
    private SysOperateLogService sysOperateLogService;

    // 注入Redis模板，用于查询token对应的user_id
    // 为什么使用StringRedisTemplate而非RedisTemplate？因为我们只需要处理字符串类型的键值对
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 定义切入点：匹配所有标记@OperateLog注解的方法
     * 环绕通知：处理所有标记@OperateLog注解的方法
     * 实现操作日志的自动记录，包括参数、结果和用户信息
     */
    @Around("@annotation(com.woniu.annotation.OperateLog)")
    public Object aroundOperate(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法签名和注解信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        OperateLog annotation = method.getAnnotation(OperateLog.class);

        // 创建操作日志对象
        SysOperateLog operateLog = new SysOperateLog();
        operateLog.setModule(annotation.module());// 从注解获取操作模块
        operateLog.setOperateType(annotation.operateType());// 从注解获取操作类型
        operateLog.setOperateContent(annotation.content());// 从注解获取操作描述
        operateLog.setOperateTime(LocalDateTime.now());// 记录操作发生时间
        operateLog.setCreateTime(LocalDateTime.now());// 设置日志创建时间
        operateLog.setCreator("system");// 日志创建者默认为系统

        // 获取请求参数并记录，根据注解配置决定是否记录
        if (annotation.recordParams()) {
            // 获取请求参数（排除ServerWebExchange）
            Object[] args = joinPoint.getArgs();
            Parameter[] parameters = method.getParameters();
            StringBuilder paramsBuilder = new StringBuilder();

            for (int i = 0; i < args.length; i++) {
                // 跳过ServerWebExchange参数，避免其无法序列化导致日志记录失败
                if (parameters[i].getType() != ServerWebExchange.class) {
                    paramsBuilder.append(parameters[i].getName()).append(":").append(args[i]).append(",");
                }
            }

            // 序列化为JSON格式
            operateLog.setRequestParams(JsonUtils.toJson(paramsBuilder.toString()));
        }

        // 获取当前登录用户ID - 已修改为从token参数查询Redis
        try {
            // 1. 尝试从方法参数中获取token（Controller中通过@RequestHeader获取的参数）
            String token = null;
            Object[] args = joinPoint.getArgs();
            String[] parameterNames = signature.getParameterNames();

            // 遍历方法参数，找到名称为token的参数
            // 为什么通过参数名查找？因为Controller中显式声明了String token参数
            if (parameterNames != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    if ("token".equals(parameterNames[i]) && args[i] instanceof String) {
                        token = (String) args[i];
                        break;
                    }
                }
            }

            // 2. 如果获取到token，则从Redis查询user_id
            if (token != null) {
                // 构造Redis key，格式：Authorization:login:token:{token值}
                // 为什么这样构造key？与前端authService.ts中存储token的键名保持一致
                String redisKey = "Authorization:login:token:" + token;
                String userIdStr = stringRedisTemplate.opsForValue().get(redisKey);

                if (userIdStr != null) {
                    // 将Redis中存储的字符串类型user_id转换为整数
                    operateLog.setUserId(Integer.parseInt(userIdStr));
                } else {
                    operateLog.setUserId(-1); // token不存在于Redis，未登录或已过期
                }
            } else {
                // 3. 未获取到token参数，回退到原StpUtil方式（兼容其他未传递token的接口）
                if (StpUtil.isLogin()) {
                    operateLog.setUserId(StpUtil.getLoginIdAsInt());
                } else {
                    operateLog.setUserId(-1); // 未登录用户标记
                }
            }
        } catch (NumberFormatException e) {
            // Redis中存储的user_id不是整数格式
            operateLog.setUserId(-3); // 用户ID格式错误标记
        } catch (Exception e) {
            // Redis查询失败或其他异常
            operateLog.setUserId(-2); // 获取用户ID失败标记
        }

        // 执行目标方法并记录结果
        Object result = joinPoint.proceed();

        // 处理响应式结果（WebFlux环境），确保响应式流正确处理
        if (result instanceof Mono) {
            return ((Mono<?>) result).flatMap(res -> {
                // 记录操作结果，根据注解配置决定是否记录
                if (annotation.recordResult()) {
                    if (res instanceof SaResult) {
                        SaResult saResult = (SaResult) res;
                        // 创建精简结果对象，只保留必要的code和msg字段
                        // 解决response_result字段数据过大的问题，避免存储敏感数据和节省空间
                        Map<String, Object> resultMap = new HashMap<>();
                        resultMap.put("code", saResult.getCode());  // 保留状态码
                        resultMap.put("msg", saResult.getMsg());    // 保留消息
                        operateLog.setResponseResult(JsonUtils.toJson(resultMap));
                    } else {
                        // 非标准响应格式仍完整记录，便于调试非预期返回类型
                        operateLog.setResponseResult(JsonUtils.toJson(res));
                    }
                }

                // 从结果中提取状态（假设使用SaResult统一响应格式）
                if (res instanceof SaResult) {
                    SaResult saResult = (SaResult) res;
                    operateLog.setStatus(saResult.getCode() == 200 ? 1 : 0);
                } else {
                    // 非SaResult格式默认成功，因为无法确定失败标准
                    operateLog.setStatus(1); // 非SaResult格式默认成功
                }

                // 保存日志并返回原始结果，使用flatMap确保日志保存完成后再返回
                return sysOperateLogService.saveOperateLog(operateLog)
                        .thenReturn(res);
            });
        }

        return result;
    }
}